/*
 *  linux/mm/memory.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * demand-loading started 01.12.91 - seems it is high on the list of
 * things wanted, and it should be easy to implement. - Linus
 */

/*
 * Ok, demand-loading was easy, shared pages a little bit tricker. Shared
 * pages started 02.12.91, seems to work. - Linus.
 *
 * Tested sharing by executing about 30 /bin/sh: under the old kernel it
 * would have taken more than the 6M I have free, but it worked well as
 * far as I could see.
 *
 * Also corrected some "invalidate()"s - I wasn't doing enough of them.
 */
/*
 * Real VM (paging to/from disk) started 18.12.91. Much more work and
 * thought has to go into this. Oh, well..
 * 19.12.91  -  works, somewhat. Sometimes I get faults, don't know why.
 *		Found it. Everything seems to work now.
 * 20.12.91  -  Ok, making the swap-device changeable like the root.
 */

#include "../include/signal.h"
#include "../include/asm/system.h"
#include "../include/linux/sched.h"
#include "../include/linux/kernel.h"

#define CODE_SPACE(addr) ((((addr)+4095)&~4095) < \
current->start_code + current->end_code)

unsigned long HIGH_MEMORY = 0;

#define copy_page(from,to) \
__asm__("cld ; rep ; movsl"::"S" (from),"D" (to),"c" (1024))

unsigned char mem_map [ PAGING_PAGES ] = {0,};



//线性地址：
//  |31     22|21   12|11       0|
//   -----------------------------
//  | 页目录项 | 页表项 | 页内偏移值 |
//  ------------------------------


// 页目录和页表中的每项信息4个字节, 其在内存中的位格式为
// |31                     12|11   9               0
// -------------------------------------------------
// |                         |     |  | | |  |U|R| |
// | page frame addr[31..12] | AVL |00|D|A|00|/|/|P|
// |                         |     |  | | |  |S|W| |
// -------------------------------------------------
// 页表信息用于描述页表及其访问属性。
// 页表地址(page table addr): 页表基址(以4k对齐, 低12位由CPU自动补0)。
// AVL: 编程自定义使用。
// D: 已修改位, 由硬件置位。
// A: 已访问位, 由硬件置位。
// R/W: 读/写位。1-可读可写; 0-仅可读或可执行。
// U/S: 用户/超级用户位, 1-运行在任何特权级的程序皆可访问;0-超级用户特权级(0,1,2)程序可访问。
// P: 页表是否可用; 1-可用;0-不可用。
// 页框地址(PAGE FRAME ADDRESS)指定了一页内存的物理起始地址。因为内存页是位于4K
// 边界上的，所以其低12 比特总是0，因此表项的低12 比特可作它用。
// 在页目录中，表项的页框地址 是一个页表的起始的物理内存地址；
// 在页表中，表项的页框地址，是内存操作的物理内存地址。
// 例如〞$pg0+7"表示：0x00001007，是页目录表中的第1项。


/*
 * Free a page of memory at physical address 'addr'. Used by
 * 'free_page_tables()'
 */
////释放物理地址 addr 开始的1页面内存。
//物理地址1MB 以下的内存空间用于内核程序和缓冲，不作为分配页面的内存空间。因此 参数 addr 需要大于 1MB。
void free_page(unsigned long addr)
{
    //首先判断参数给定的物理地址 addr 的合理性。
    // 如果物理地址 addr 小于内存低端（1MB），则表示在内核程序或高速缓冲中，对此不予处理。
	if (addr < LOW_MEM) return;
    // 如果物理地址 addr >= 系统所含物理内存最高端，则显示出错信息并且内核停止工作。
	if (addr >= HIGH_MEMORY)
		panic("trying to free nonexistent page");
    //根据addr算出从内存低端开始计起的内存页面号。
    // 页面号= (addr - LOW_MEM) /4096。页面号从0号开始计起。
    // 此时 addr 中存放着页面号。
	addr -= LOW_MEM;
	addr >>= 12;
    // 如果该页面号对应的页面映射字节不等于0，则减1返回。此时该映射字节值应该为0，表示页面己释放。
	if (mem_map[addr]--) return;
    // 如果对应页面字节原本就是0，表示该物理页面本来就是空闲的，说明内核代码出问题。于是显示出错信息并停机。
	mem_map[addr]=0;
	panic("trying to free free page");
}

/*
 * This function frees a continuos block of page tables, as needed
 * by 'exit()'. As does copy_page_tables(), this handles only 4Mb blocks.
 */
///根据指定的线性地址和限长（页表个数），释放对应内存页表指定的内存块并置表项空闲。
// 页目录: 位于物理地址0开始处，共1024项，每项4字节，共占4K字节。
// 目录项: 页目录的每一项叫做目录项，一个目录项指定一个页表。
// 页表:  每个页表有 1024项，每项4字节。一个页表占4K内存。内核页表从物理地址 Ox1000 处开始（紧按着目录空间），共4个页表。
//        各进程（除了在内核代码中的进程0和1）的页表所占据的页面在进程被创建时由内核为其在主内存区申请得到。
// 页表项: 每个页表项对应1页物理内存4K，每个页表有 1024个页表项。因此一个页表最多可映射 4MB 的物理内存。 1024 * 4K = 4M
//参数：from 一 起始线性基地址；size 释放的字节长度。
int free_page_tables(unsigned long from,unsigned long size)
{
	unsigned long *pg_table;
	unsigned long * dir, nr;

    // 所有页面占据的空间：1024 * 4K = 4M

    //检测参数 from (线性基地址)是否在 4MB 的边界处。因为该函数只能处理这种情况。
	if (from & 0x3fffff)
		panic("free_page_tables called with wrong alignment");
    //若from=0，则出错。说明试图释放内核和缓冲所占空间。
	if (!from)
		panic("Trying to free up swapper memory space");

    // 计算参数 size 给出的长度所占的页目录项数（4MB 的进位整数倍），也即所占页表数。
    // 因为1个页表可管理 4MB 物理内存，所以这里用右移22 位的方式把需要复制的内存长度值 除以4MB。
    // 其中加上 0x3fffff（即 4Mb-1）用于得到进位整数倍结果，即除操作若有余数 则进1。
    // 例如，如果原 size= 4.01Mb，那么可得到结果 size=2。
	size = (size + 0x3fffff) >> 22;
    // 计算from线性基地址对应的目录项起始物理地址。
    // 目录项号= from>>22，线性地址的31-22位，用来表示一个页目录项的索引号
    // 目录项起始物理内存地址 =目录项号<<2(每个)，也即(from>>20)。每个目录项是4 byte，物理内存的寻找单位是byte，一个地址对应一个byte
    //
    // 该目录项对应的物理内存地址 = 页目录的起始物理地址(0,是在head.s这样存放的) + 目录项号 * 4 = (from>>22) * 4 = (from>>22)<<2 = (from>>20) & 0xffc
    // “与〞上 Oxffc 确保目录项指针范围有效，即用于屏蔽目录项指针最后2位。因为只移动了20位，因此最后 2 位是页表项索引的内容，应屏蔽掉。
	dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
    // 此时 size 是释放的页表个数，即页目录项数。 dir是起始目录项指针。
    // 现在开始循环操作页目录项，依次释放每个页表中的页表项。
	for ( ; size-->0 ; dir++) {
        // dir是目录项的物理地址，*dir是目录项内容，
        // 如果该目录项无效（P位=0），表示该 目录项没有使用（对应的页表不存在），则继续处理下一个目录项。
		if (!(1 & *dir))
			continue;
        // 从目录项中取出页表地址 pg_table，
		pg_table = (unsigned long *) (0xfffff000 & *dir);
        // 对该页表中的 1024 个表项进行处理，释放有效页表项（P位=1）对应的物理内存页面。
		for (nr=0 ; nr<1024 ; nr++) {
            if (*pg_table) {
                if (1 & *pg_table)
                    free_page(0xfffff000 & *pg_table);
                else
                    swap_free(*pg_table >> 1);
                // 把该页表项清零，并继续处理下一页表项。
                *pg_table = 0;
            }
            pg_table++;
		}
        // 当一个页表所有表项都处理完毕就释放该页表自身占据的内存页面，并继续处理下一页目录项。
		free_page(0xfffff000 & *dir);
		*dir = 0;
	}
    // 最后刷新页变换高速缓冲，并返回0。
	invalidate();
	return 0;
}

/*
 *  Well, here is one of the most complicated functions in mm. It
 * copies a range of linerar addresses by copying only the pages.
 * Let's hope this is bug-free, 'cause this one I don't want to debug :-)
 *
 * Note! We don't copy just any chunks of memory - addresses have to
 * be divisible by 4Mb (one page-directory entry), as this makes the
 * function easier. It's used only by fork anyway.
 *
 * NOTE 2!! When from==0 we are copying kernel space for the first
 * fork(). Then we DONT want to copy a full page-directory entry, as
 * that would lead to some serious memory waste - we just copy the
 * first 160 pages - 640kB. Even that is more than we need, but it
 * doesn't take any more memory - we don't copy-on-write in the low
 * 1 Mb-range, so the pages can be shared with the kernel. Thus the
 * special case for nr=xxxx.
 */
int copy_page_tables(unsigned long from,unsigned long to,long size)
{
	unsigned long * from_page_table;
	unsigned long * to_page_table;
	unsigned long this_page;
	unsigned long * from_dir, * to_dir;
    unsigned long new_page;
	unsigned long nr;

	if ((from&0x3fffff) || (to&0x3fffff))
		panic("copy_page_tables called with wrong alignment");
    //从线性地址from，算出物理地址from_dir
	from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
	to_dir = (unsigned long *) ((to>>20) & 0xffc);
    //算出占用的页表数(也是页目录项的个数)
	size = ((unsigned) (size+0x3fffff)) >> 22;
	for( ; size-->0 ; from_dir++,to_dir++) {
		if (1 & *to_dir)
			panic("copy_page_tables: already exist");
		if (!(1 & *from_dir))
			continue;
		from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
		if (!(to_page_table = (unsigned long *) get_free_page()))
			return -1;	/* Out of memory, see freeing */
        //设置目的目录项信息，把最后3位置位，即当前目的目录项“或”上7，表示该 页表 映射的内存存页面是用户级的，并且可读写、存在(Usr，R/w， Present)。
        // U/S位是0，则R/W 就没有作用。
        // U/S是1，而R是0，那么运行在用户层的代码就只能读页面。
        // U/S 和R/W 都置位，则就有读写的权限。
		*to_dir = ((unsigned long) to_page_table) | 7;
        // 对当前处理的页目录项对应的页表，设置需要复制的页面项数。
        // 如果是在内核空间，则仅需复制头160 页对应的页表项 1/ (nr = 160），对应于开始 640KB 物理内存。
        // 否则需要复制一个页表中的所有1024个页表项 (nr = 1024)，可映射4MB 物理内存。
		nr = (from==0)?0xA0:1024;
		for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
			this_page = *from_page_table;
            if (!this_page)
                continue;
            if (!(1 & this_page)) {
                if (!(new_page = get_free_page()))
                    return -1;
                read_swap_page(this_page>>1, (char *) new_page);
                *to_page_table = this_page;
                *from_page_table = new_page | (PAGE_DIRTY | 7);
                continue;
            }
            //复位表中的 R/W 标志，位1置0
			this_page &= ~2;
			*to_page_table = this_page;

            // 判断该页表项所指物理页面的地址是否在 1MB 以上，位于 1MB以下的页面，是内核页面，因此不需要对 mem map[]进行设置。
            // 因此对于内核移动到任务0中并且调用 fork(创建任务1时（用于运行 init.0〉，由于此时复制的页面还仍然都在内核代码区域，以下判断中的语句不会执行，任务1的页面仍然可以随时读写。
			if (this_page > LOW_MEM) {
                // 令源页表项所指内存页也为只读。因为现在开始有两个进程共用内存区了。
                // 若其中1个进程需要进行写操作，则可以通过页异常写保护处理为执行写操作的进程分配1页新空闲页面，也即进行写时复制（copy on write)操作。
				*from_page_table = this_page;
                //计算页面号
				this_page -= LOW_MEM;
				this_page >>= 12;
                //增加对应页面的引用次数，mem nap[]仅用于管理主内存区中的页面使用情况。
				mem_map[this_page]++;
			}
		}
	}
	invalidate();
	return 0;
}

/*
 * This function puts a page in memory at the wanted address.
 * It returns the physical address of the page gotten, 0 if
 * out of memory (either when trying to access page-table or
 * page.)
 */
////把一物理内存页面映射到线性地址空间指定处。
// 主要工作是在相关页目录项和页表项中设置指定页面的信息。若成功则返回物理页面地址。
// 在 处理缺页异常的C函数 do no page(）中会调用此函数。
// 对于缺页引起的异常，由于任何缺页缘故而对页表作修改时，并不需要刷新 CPU 的页变换缓冲（或称 Translation Lookaside // Buffer - TLB)，即使页表项中标志P被从口修改成1。
// 因为无效页项不会被缓冲，因此当修改了一个无效的页表项时不需要刷新。 在此就表现为不用调用 Invalidate()函数。
// 参数 page 是分配的主内存区中某一页面（页帧，页框）的索引值；address 是线性地址。
static unsigned long put_page(unsigned long page,unsigned long address)
{
	unsigned long tmp, *page_table;

/* NOTE !!! This uses the fact that _pg_dir=0 */
    // 如果该页面位置低于 LOW_MEM (1MB)或 超出系统实际含有内存高端 HIGH_MEMORY，则发出警告。
    // LOW_MEM 是主内存区可能有的最小起始位置。当系统物理内存小于或等于 6MB 时，主内存区起始于 LOW_MEM 处。

	if (page < LOW_MEM || page >= HIGH_MEMORY)
		printk("Trying to put page %p at %p\n",page,address);
    // 查看一下该 page 页面是否是己经申请的页面，即判断其在内存页面映射字节图 mem_map[]中相应字节是否已经置位。
	if (mem_map[(page-LOW_MEM)>>12] != 1)
        // 若没有则需发出警告。
		printk("mem_map disagrees with %p at %p\n",page,address);

    //根据线性地址 address 计算其在页目录表中对应的目录项指针
	page_table = (unsigned long *) ((address>>20) & 0xffc);
	if ((*page_table)&1)
        // 如果该目录项有效（P=1），即指定的页表在内存中，则从中取得指定页表地址放到 page_table 变量中。
		page_table = (unsigned long *) (0xfffff000 & *page_table);
	else {
        // 否则就申请一空闲页面给页表使用，
        if (!(tmp=get_free_page()))
			return 0;
        // 对应目录项中置相应标志（7 - User、U/S、R/W）。
		*page_table = tmp|7;
        // 将该页表地址放到 page_table 变量中。参见对115行语句的说明。
		page_table = (unsigned long *) tmp;
	}
    //最后在找到的页表 page_ table 中设置页表项内容，即把物理页面 page 的地址填入表 项同时置位3个标志（U/S、Ww/R、P〉。
    // 该页表项在页表中的索引值等于线性地址位21-位12 组成的10 比特的值。每个页表共可有1024项(0 - 0x3ff)
	page_table[(address>>12) & 0x3ff] = page | 7;
/* no need for invalidate */
	return page;
}

/*
 * The previous function doesn't work very well if you also want to mark
 * the page dirty: exec.c wants this, as it has earlier changed the page,
 * and we want the dirty-status to be correct (for VM). Thus the same
 * routine, but this time we mark it dirty too.
 */
unsigned long put_dirty_page(unsigned long page, unsigned long address)
{
    unsigned long tmp, *page_table;

/* NOTE !!! This uses the fact that _pg_dir=0 */

    if (page < LOW_MEM || page >= HIGH_MEMORY)
        printk("Trying to put page %p at %p\n",page,address);
    if (mem_map[(page-LOW_MEM)>>12] != 1)
        printk("mem_map disagrees with %p at %p\n",page,address);
    page_table = (unsigned long *) ((address>>20) & 0xffc);
    if ((*page_table)&1)
        page_table = (unsigned long *) (0xfffff000 & *page_table);
    else {
        if (!(tmp=get_free_page()))
            return 0;
        *page_table = tmp|7;
        page_table = (unsigned long *) tmp;
    }
    page_table[(address>>12) & 0x3ff] = page | (PAGE_DIRTY | 7);
/* no need for invalidate */
    return page;
}

void un_wp_page(unsigned long * table_entry)
{
	unsigned long old_page,new_page;

	old_page = 0xfffff000 & *table_entry;
	if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {
		*table_entry |= 2;
		invalidate();
		return;
	}
	if (!(new_page=get_free_page()))
		oom();
	if (old_page >= LOW_MEM)
		mem_map[MAP_NR(old_page)]--;
	copy_page(old_page,new_page);
	*table_entry = new_page | 7;
	invalidate();
}

/*
 * This routine handles present pages, when users try to write
 * to a shared page. It is done by copying the page to a new address
 * and decrementing the shared-page counter for the old page.
 *
 * If it's in code space we exit with a segment error.
 */
void do_wp_page(unsigned long error_code,unsigned long address)
{

    if (address < TASK_SIZE)
        printk("\n\rBAD! KERNEL MEMORY WP-ERR!\n\r");
    if (address - current->start_code > TASK_SIZE) {
        printk("Bad things happen: page error in do_wp_page\n\r");
        do_exit(SIGSEGV);
    }
#if 0
/* we cannot do this yet: the estdio library writes to code space */
/* stupid, stupid. I really want the libc.a from GNU */
	if (CODE_SPACE(address))
		do_exit(SIGSEGV);
#endif
	un_wp_page((unsigned long *)
		(((address>>10) & 0xffc) + (0xfffff000 &
		*((unsigned long *) ((address>>20) &0xffc)))));

}

void write_verify(unsigned long address)
{
	unsigned long page;

	if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1))
		return;
	page &= 0xfffff000;
	page += ((address>>10) & 0xffc);
	if ((3 & *(unsigned long *) page) == 1)  /* non-writeable, present */
		un_wp_page((unsigned long *) page);
	return;
}

void get_empty_page(unsigned long address)
{
	unsigned long tmp;

	if (!(tmp=get_free_page()) || !put_page(tmp,address)) {
		free_page(tmp);		/* 0 is ok - ignored */
		oom();
	}
}

/*
 * try_to_share() checks the page at address "address" in the task "p",
 * to see if it exists, and if it is clean. If so, share it with the current
 * task.
 *
 * NOTE! This assumes we have checked that p != current, and that they
 * share the same executable or library.
 */
////尝试对当前进程指定地址处的页面进行共享处理。
//当前进程与进程p是同一执行代码，也可以认为当前进程是由p进程执行 fork操作产生的
//进程，因此它们的代码内容一样。如果未对数据段内容作过修改那么数据段内容也应一样。
//参数 address 是进程中的逻辑地址，即是当前进程欲与 p进程共享页面的逻辑页面地址。
//进程p是将被共享页面的进程。如果p进程 address 处的页面存在并且没有被修改过的话，
//就让当前进程与p进程共享之。同时还需要验证指定的地址处是否己经申请了页面，若是
//则出错，死机。返回：1- 页面共享处理成功；0-失败。
static int try_to_share(unsigned long address, struct task_struct * p)
{
	unsigned long from;
	unsigned long to;
	unsigned long from_page;
	unsigned long to_page;
	unsigned long phys_addr;


    //address 是以start_code为基址，所以address+start_code 是线性地址，再 >>20 就是线性空间的目录项
	from_page = to_page = ((address>>20) & 0xffc);
	from_page += ((p->start_code>>20) & 0xffc);
	to_page += ((current->start_code>>20) & 0xffc);
/* is there a page-directory at from? */

    // 先对p进程的表项进行操作。目标是取得p进程中 address 对应的物理内存页面地址，并且该物理页面存在，而且干净（没有被修改过，不脏）。
    // 取目录项内容。
	from = *(unsigned long *) from_page;   //p进程目录项内容
    // 如果该目录项无效（P=0），表示目录项对应的二级页表不存在， 于是返回。
	if (!(from & 1))
		return 0;
    // 取该目录项对应页表地址 from，从而计算出逻辑地址 address 对应的页表项指针，
	from &= 0xfffff000;
	from_page = from + ((address>>10) & 0xffc);
    // 取出该页表项内容临时保存在 phys_addr 中。
	phys_addr = *(unsigned long *) from_page;
/* is the page clean and present? */
    //检查页表项映射的物理页面是否存在并且干净。
    // 0x41 对应页表项中的D（Dirty）和P(Present）标志。
	if ((phys_addr & 0x41) != 0x01)
        // 如果页面不干净或无效则返回。
		return 0;
    // 从该表项中取出物理页面地址,保存在 phys_addr 中。
	phys_addr &= 0xfffff000;  //物理页面地址
    // 检查一下这个物理页面地址的有效性，即它不应该超过 机器最大物理地址值，也不应该小于内存低端(1MB)。
	if (phys_addr >= HIGH_MEMORY || phys_addr < LOW_MEM)
		return 0;

    // 对当前进程的表项进行操作。目标是取得当前进程中 address 对应的页表项地址，并且该页表项还没有映射物理页面，即其P=0。

    //取当前进程页目录项内容->to
	to = *(unsigned long *) to_page;  //当前进程目录项内容
    // 如果该目录项无效（P=0），即目录项对应的二级页表不存在，
	if (!(to & 1)) {
        // 则申请一空闲页面来存放页表，
		if ((to = get_free_page()))
            // 并更新目录项 to_page 内容，让其指向该内存页面。
			*(unsigned long *) to_page = to | 7;
		else
			oom();
	}
    //取目录项中的页表地址＞to，加上页表项索引值<<2，即页表项在表中偏移地址，得到页表项地址＞to_page。
	to &= 0xfffff000;   //页表地址
	to_page = to + ((address>>10) & 0xffc); //页表项地址
    // 针对该页表项，如果此时我们检查出其对应的物理页面已经存在，即页表项的存在位 P=1，
    // 则说明原本想共享进程p中对应的物理页面，但现在我们自己已经占有了（映射有）物理页面。于是说明内核出错，死机。
	if (1 & *(unsigned long *) to_page)
		panic("try_to_share: to_page already exists");
/* share them: write-protect */
    //在找到了进程p中逻辑地址 address 处对应的干净且存在的物理页面，而且也确定了当前 进程中逻辑地址 address 所对应的二级页表项地址之后，我们现在对他们进行共享处理。

    // 方法很简单，就是首先对p进程的页表项进行修改，设置其写保护（R/W=0，只读）标志，
	*(unsigned long *) from_page &= ~2;
    //然后让当前进程复制 p进程的这个页表项。此时当前进程逻辑地址 address 处页面即被映射到 p进程逻辑地址 address 处页面映射的物理页面上
	*(unsigned long *) to_page = *(unsigned long *) from_page;
	invalidate();
	phys_addr -= LOW_MEM;
	phys_addr >>= 12;
	mem_map[phys_addr]++;
	return 1;
}

/*
 * share_page() tries to find a process that could share a page with
 * the current one. Address is the address of the wanted page relative
 * to the current data space.
 *
 * We first check if it is at all feasible by checking executable->i_count.
 * It should be >1 if there are other tasks sharing this inode.
 */
////共享页面处理。
// 在发生缺页异常时，首先看看能否与运行同一个执行文件的其他进程进行页面共享处理。
// 首先判断系统中是否有另一个进程也在运行当前进程一样的执行文件。
// 若有，则在系统当前所有任务中寻找这样的任务。
// 若找到了这样的任务就尝试与其共享指定地址处的页面。
// 若系统中没有其他任务正在运行与当前进程相同的执行文件，那么共享页面操作的前提条件不存在，因此函数立刻退出。

// 判断系统中是否有另一个进程也在执行同一个执行文件的方法是利用进程任务数据结构中的 executable 字段。
// 该字段指向进程正在执行的程序在内存中的 i节点。根据该 i节点的引用次数 i_count 就可判断。
// 若 executable->i_count 值大于1，则表明系统中可能有两个进程在运行同一个执行文件，
// 于是可以再对任务结构数组中所有任务比较是否有相同的 executable 字段来最后确定多个进程运行着相同执行文件的情况。
// 参数 address 是进程中的逻辑地址，即是当前进程欲与p进程共享页面的逻辑页面地址。
// 返回1- 共享操作成功，0-失败。
static int share_page(struct m_inode * inode, unsigned long address)
{
    struct task_struct ** p;

    if (inode->i_count < 2 || !inode)
        return 0;
    for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) {
        if (!*p)
            continue;
        if (current == *p)
            continue;
        if (address < LIBRARY_OFFSET) {
            if (inode != (*p)->executable)
                continue;
        } else {
            if (inode != (*p)->library)
                continue;
        }
        if (try_to_share(address,*p))
            return 1;
    }
    return 0;
}




////执行缺页处理。
//当访问不存在页面处理函数。页异常中断处理过程中调用的函数。在 page. s 程序中被调用。
// 函数参数 error_code 和 address 是进程在访问页面时由 CPU 因缺页产生异常而自动生成。
// error_code 出错类型，参见本章开始处的“内存页面出错异常”一节；
// address 是产生异常的页面线性地址。
// 该函数首先尝试与已加载的相同文件进行页面共享，
// 或者只是由于进程动态申请内存页面而只需映射一页物理内存页即可。
// 若共享操作不成功，那么只能从相应文件中读入所缺的数据页面到指定线性地址处。
void do_no_page(unsigned long error_code,unsigned long address)
{
	int nr[4];
	unsigned long tmp;
	unsigned long page;
	int block,i;

    struct m_inode * inode;

    if (address < TASK_SIZE)
        printk("\n\rBAD!! KERNEL PAGE MISSING\n\r");
    if (address - current->start_code > TASK_SIZE) {
        printk("Bad things happen: nonexistent page error in do_no_page\n\r");
        do_exit(SIGSEGV);
    }
    page = *(unsigned long *) ((address >> 20) & 0xffc);
    if (page & 1) {
        page &= 0xfffff000;
        page += (address >> 10) & 0xffc;
        tmp = *(unsigned long *) page;
        if (tmp && !(1 & tmp)) {
            swap_in((unsigned long *) page);
            return;
        }
    }
    //首先取线性空间中指定地址 address 处页面地址。一页4K,12位，
	address &= 0xfffff000;
    // 算出逻辑地址，即 线性地址在进程空间中相对于进程基址的偏移长度值 tmp。
	tmp = address - current->start_code;

    if (tmp >= LIBRARY_OFFSET ) {
        inode = current->library;
        block = 1 + (tmp-LIBRARY_OFFSET) / BLOCK_SIZE;
    } else if (tmp < current->end_data) {
        inode = current->executable;
        block = 1 + tmp / BLOCK_SIZE;
    } else {
        inode = NULL;
        block = 0;
    }
    if (!inode) {
        get_empty_page(address);
        return;
    }


    //否则说明所缺页面在进程执行影像文件范围内，于是就尝试共享页面操作，若成功则退出。
    if (share_page(inode,tmp))
		return;
    //共享页面失败就只能申请一页物理内存页面 page，
    // 然后从设备上读取执行文件中的相应页面并放置（映射）到进程页面逻辑地址 tmp 处。
	if (!(page = get_free_page()))
		oom();
    /* remember that 1 block is used for header */
    // 因为块设备上存放的 可执行文件映像 第1块数据是程序头结构，因此在读取该文件时需要跳过 第1块数据。
    // 先计算缺页所在的数据块号。 进程逻辑地址 tmp 除以数据块大小再加1即可得出缺少的页面在执行映像文件中的起始块号 block。

	block = 1 + tmp/BLOCK_SIZE;
    //因为每块数据长度为 BLOCK_SIZE = 1KB，因此一页内存可存放4个数据块。所以读取4个block
	for (i=0 ; i<4 ; block++,i++)
        // 根据这个块号和执行文件的i节点，我们 就可以从映射位图中找到对应块设备中对应的设备逻辑块号（保存在 nr[]数组中）。
        nr[i] = bmap(inode,block);
    // 利用bread page() 即可把这4个逻辑块读入到物理页面 page 中。
    bread_page(page,inode->i_dev,nr);
    // 在读设备逻辑块操作时，可能会出现这样一种情况，即在执行文件中的读取页面位置可能离文件尾不到1个页面的长度。
    // 因此就可能读入一些无用的信息。下面的操作就是把这部分超出执行文件 end_data 以后的部分清零处理。
	i = tmp + 4096 - current->end_data;   //i 超出的字节长度值。
    if (i>4095)
        i = 0;
	tmp = page + 4096;   // tmp 指向页面末端。
	while (i-- > 0) {    // 页面末端i字节清零。
		tmp--;
		*(char *)tmp = 0;
	}
    //最后把引起缺页异常的一页物理页面映射到指定线性地址 address 处。
    // 若操作成功就返回。
	if (put_page(page,address))
		return;
    // 否则就释放内存页，显示内存不够。
	free_page(page);
	oom();
}

void mem_init(long start_mem, long end_mem)
{
	int i;

	HIGH_MEMORY = end_mem;
    // The function first sets the memory mapped byte array items corresponding to all pages in
    // the range of 1MB to 16MB to the occupied state, that is, all bytes are set to USED (100).
    // PAGING_PAGES is defined as (PAGING_MEMORY>>12), which is the number of all physical memory
    // pages above 1MB (15MB/4KB = 3840).
	for (i=0 ; i<PAGING_PAGES ; i++)
		mem_map[i] = USED;
    //从1M 开始计算，每4K 计数加1 ，计算start_mem 位于内存映射字节数组中的项号。
	i = MAP_NR(start_mem);
	end_mem -= start_mem;
	end_mem >>= 12;
    // 当前代码是 把4M-16M的内存 映射字节数组项设置为空闲状态
	while (end_mem-->0)
		mem_map[i++]=0;
}

void show_mem(void)
{
    int i,j,k,free=0,total=0;
    int shared=0;
    unsigned long * pg_tbl;

    printk("Mem-info:\n\r");
    for(i=0 ; i<PAGING_PAGES ; i++) {
        if (mem_map[i] == USED)
            continue;
        total++;
        if (!mem_map[i])
            free++;
        else
            shared += mem_map[i]-1;
    }
    printk("%d free pages of %d\n\r",free,total);
    printk("%d pages shared\n\r",shared);
    k = 0;
    for(i=4 ; i<1024 ;) {
        if (1&pg_dir[i]) {
            if (pg_dir[i]>HIGH_MEMORY) {
                printk("page directory[%d]: %08X\n\r",
                       i,pg_dir[i]);
                continue;
            }
            if (pg_dir[i]>LOW_MEM)
                free++,k++;
            pg_tbl=(unsigned long *) (0xfffff000 & pg_dir[i]);
            for(j=0 ; j<1024 ; j++)
                if ((pg_tbl[j]&1) && pg_tbl[j]>LOW_MEM)
                    if (pg_tbl[j]>HIGH_MEMORY)
                        printk("page_dir[%d][%d]: %08X\n\r",
                               i,j, pg_tbl[j]);
                    else
                        k++,free++;
        }
        i++;
        if (!(i&15) && k) {
            k++,free++;	/* one page/process for task_struct */
            printk("Process %d: %d pages\n\r",(i>>4)-1,k);
            k = 0;
        }
    }
    printk("Memory found: %d (%d)\n\r",free-shared,total);
}